use rust_decimal::Decimal;
use crate::parser::{Token, TokenStream, Parser};
use crate::Result;

#[derive(Debug, Clone)]
pub enum Spec {
    Decimal(DecimalSpec),
    String(StringSpec),
}

impl Parser for Spec {
    fn parse(stream: &mut TokenStream, offset: u32) -> Result<Spec> {
        if let Ok(spec) = StringSpec::try_parse(stream, offset) {
            Ok(Spec::String(spec))
        } else if let Ok(spec) = DecimalSpec::try_parse(stream, offset) {
            Ok(Spec::Decimal(spec))
        } else {
            Err(stream.parse_error("SPEC"))
        }
    }
}

impl Spec {
    pub fn length(&self) -> u32 {
        match self {
            Spec::Decimal(d) => d.length(),
            Spec::String(s)  => s.length,
        }
    }
}

#[derive(Debug, PartialEq, Clone, Copy)]
pub enum DecimalFormat {
    Packed, UnsignedFullWord, None,
}

#[derive(Debug, Clone)]
pub struct Sign {
    pub leading: bool,
    pub separate: bool,
}

#[derive(Debug, Clone)]
pub struct DecimalSpec {
    pub precision: u32,
    pub scale: Option<u32>,
    pub sign: Option<Sign>,
    pub format: DecimalFormat,
    pub value: Option<Decimal>,
}

impl DecimalSpec {
    pub fn length(&self) -> u32 {
        let full_length = self.precision + self.scale.unwrap_or(0);
        match self.format {
            DecimalFormat::None => full_length,
            DecimalFormat::Packed =>  (full_length as f32 / 2 as f32).ceil() as u32,
            DecimalFormat::UnsignedFullWord => (full_length as f32).log2().ceil() as u32,
        }
    }
}

impl Parser for DecimalSpec {
    fn parse(stream: &mut TokenStream, _offset: u32) -> Result<DecimalSpec> {
        let mut signed = false;
        let token = stream.next();
        match token {
            Token::Unsigned(9) => {},
            Token::Ident(s) => {
                let mut chars = s.chars();
                while let Some(c) = chars.next() {
                    match c {
                        'S' => signed = true,
                        'V' => {},
                        '9' => { break; },
                        _ => return Err(stream.parse_error("deciaml spec")),
                    }
                }
            },
            _ => return Err(stream.parse_error("deciaml spec")),
        }
        stream.must_be(Token::OpenBrace)?;
        let precision = stream.parse_unsigned()?;
        stream.must_be(Token::CloseBrace)?;
        let scale = if let Ok(ident) = stream.parse_ident() {
            if &ident == "V9" {
                stream.must_be(Token::OpenBrace)?;
                let scale = Some(stream.parse_unsigned()?);
                stream.must_be(Token::CloseBrace)?;
                scale
            } else {
                None
            }
        } else {
            None
        };
        let format = if stream.may_be(Token::Comp){
            DecimalFormat::UnsignedFullWord
        } else if stream.may_be(Token::Comp3){
            DecimalFormat::Packed
        } else {
            DecimalFormat::None
        };
        let sign_leading = stream.may_be(Token::Leading);
        let sign_separate = stream.may_be(Token::Separate);
        let sign = if signed {
            Some(Sign { leading: sign_leading, separate: sign_separate })
        } else {
            None
        };
            
        let value = if stream.may_be(Token::Value) {
            let val = stream.parse_decimal()?;
            Some(val)
        } else {
            None
        };
        Ok(DecimalSpec{scale, precision, sign, format, value})
    }
}

#[derive(Debug, Clone)]
pub struct StringSpec {
    pub length: u32,
    pub value: Option<String>,
}

impl Parser for StringSpec {
    fn parse(stream: &mut TokenStream, _offset: u32) -> Result<StringSpec> {
        let stype = stream.parse_ident()?;
        let first = stype.chars().next();
        if first == Some('X') || first == Some('A') {
            let length = if stream.may_be(Token::OpenBrace) {
                let length = stream.parse_unsigned()?;
                stream.must_be(Token::CloseBrace)?;
                length
            } else {
                stype.len() as u32
            };
            let value = if stream.may_be(Token::Value) {
                let val = stream.parse_string()?;
                Some(val)
            } else {
                None
            };
            Ok(StringSpec{ length, value })
        } else {
            Err(stream.parse_error("string spec"))
        }
    }
}
